home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 11 code / Async Sound Helper / Sound Helper Demo / SoundHelper.c < prev    next >
Encoding:
Text File  |  1995-04-14  |  61.1 KB  |  1,715 lines  |  [TEXT/MPCC]

  1. //=======================================================================================
  2. //
  3. // SoundHelper.c - the Asynchronous Sound Helper
  4. //
  5. // Written by Bryan K. Ressler (Beaker), 2/4/92
  6. //    Updated by Bradley D. Mohr, 2/4/95 for Easy Puzzle.
  7. //
  8. // Version 1.00, 2/4/92        Original version
  9. //         1.01, 2/20/92    Added SHGetState to fix handle semaphore problem
  10. //         1.02, 3/21/92    Make handle/error checks more explicit, modify
  11. //                             SHPlayStopByRec (to handle channels with no synthesizer)
  12. //         1.03, 4/4/92        Modifications per KO, RC, JR.  Public routines now pascal.
  13. //         1.04, 4/20/92    Reverted to SHPlayPause/SHPlayContinue structure.
  14. //           1.04a 2/4/95        Updated to work with Universal Headers & SM 3.0 (BDM)
  15. //
  16. // The author wishes to acknowledge Jim Reekes, Kip Olsen, Neil Day, and Rich Collyer
  17. // for their vital technical help in the preparation of this code for publication.
  18. //
  19. //=======================================================================================
  20.  
  21. //=======================================================================================
  22. // Includes
  23. //=======================================================================================
  24. #include "Std.h"
  25. #include "SoundHelper.h"
  26. #include "SHPrivate.h"
  27.  
  28. //=======================================================================================
  29. // Statics
  30. //=======================================================================================
  31. static Boolean            gsSHInited = false;            // Flags whether Helper has been inited
  32. static Boolean            *gsSHNeedsTime;                // Pointer to app "SH needs time" flag
  33.  
  34. static SHOutputVars        gsSHOutVars;                // Sound output variables
  35. static SHInputVars        gsSHInVars;                    // Sound input variables
  36.  
  37. static SndCallBackUPP    gsSHPlayCompletionUPP;        // UPP for SoundCallBackProc     (BDM)
  38. static SICompletionUPP    gsSHRecordCompletionUPP;    // UPP for SIRecordCompletion    (BDM)
  39.  
  40. //=======================================================================================
  41. // Static prototypes
  42. //=======================================================================================
  43. static long SHNewRefNum(void);
  44. static OSErr SHNewOutRec(SHOutPtr *outRec);
  45. static pascal void SHPlayCompletion(SndChannelPtr channel, SndCommand *command);
  46. static pascal void SHRecordCompletion(SPBPtr inParams);
  47. static OSErr SHInitOutRec(SHOutPtr outRec, long refNum, Handle sound, char handleState);
  48. static void SHReleaseOutRec(SHOutPtr outRec);
  49. static OSErr SHQueueCallback(SndChannel *channel);
  50. static OSErr SHBeginPlayback(SHOutPtr outRec);
  51. static char SHGetState(Handle snd);
  52. static SHOutPtr SHOutRecFromRefNum(long refNum);
  53. static void SHPlayStopByRec(SHOutPtr outRec);
  54. static OSErr SHGetDeviceSettings(long inRefNum, short *numChannels, Fixed *sampleRate,
  55.     short *sampleSize, OSType *compType);
  56.  
  57. //=======================================================================================
  58. //
  59. //    pascal OSErr SHInitSoundHelper(Boolean *attnFlag, short numChannels)
  60. //
  61. //    Summary:
  62. //        This routine initializes the Asynchronous Sound Helper.
  63. //
  64. //    Scope:
  65. //        Public.
  66. //
  67. //    Parameters:
  68. //        attnFlag        Pointer to a application Boolean.  This Boolean will be set to
  69. //                        true when the Helper needs a call to SHIdle.  For example, the
  70. //                        application will have a global Boolean with a name like
  71. //                        "gCallHelper," and will pass the address of that global to
  72. //                        SHInitSoundHelper.  Then, in the application's main event loop,
  73. //                        will simply check gCallHelper, and call SHIdle if it is set.
  74. //        numChannels        Tells the Helper how many output records to allocate. The number
  75. //                        of simultaneous sounds that can be produced by the Helper is
  76. //                        limited by a) the number of simultaneous channels the Sound
  77. //                        Manager allows, and b) the number of output records specified by
  78. //                        this parameter. If you specify zero, a reasonable default (4) is
  79. //                        used.
  80. //
  81. //    Returns:
  82. //        MemError()        If there is one.
  83. //        memFullErr        If the output array was nil but MemError returned noErr.
  84. //        noErr            Otherwise.
  85. //
  86. //    Operation:
  87. //        This routine simply allocates the output records, initializes some statics, and
  88. //        sets a flag that tells whether the Helper has been initialized.
  89. //
  90. //=======================================================================================
  91. pascal OSErr SHInitSoundHelper(Boolean *attnFlag, short numChannels)
  92. {
  93.     OSErr    err;
  94.     
  95.     // Use default number of channels if zero was specified
  96.     if (numChannels == 0)
  97.         numChannels = kSHDefChannels;
  98.     
  99.     // Remember the address of the application's "attention" flag
  100.     gsSHNeedsTime = attnFlag;
  101.     
  102.     // Allocate the channels
  103.     gsSHOutVars.numOutRecs = numChannels;
  104.     gsSHOutVars.outArray = (SHOutPtr)NewPtrClear(numChannels * sizeof(SHOutRec));
  105.  
  106.     // Set up UPPs for play completion and input completion (BDM)
  107.     gsSHPlayCompletionUPP     = NewSndCallBackProc(SHPlayCompletion);
  108.     gsSHRecordCompletionUPP = NewSICompletionProc(SHRecordCompletion);
  109.     
  110.     // If successful, flag that we're initialized and exit
  111.     if (gsSHOutVars.outArray != nil) {
  112.         gsSHInited = true;
  113.         return(noErr);
  114.     } else {
  115.         // Return some kind of error (MemError if there is one, otherwise make one up)
  116.         err = MemError();
  117.         if (err == noErr)
  118.             err = memFullErr;
  119.         return(err);
  120.     }
  121. }
  122.  
  123. //=======================================================================================
  124. //
  125. //    pascal void SHIdle(void)
  126. //
  127. //    Summary:
  128. //        This routine performs various cleanup operations when sounds have finished
  129. //        playing or recording.
  130. //
  131. //    Scope:
  132. //        Public.
  133. //
  134. //    Parameters:
  135. //        None.
  136. //
  137. //    Returns:
  138. //        Nothing.
  139. //
  140. //    Operation:
  141. //        First, SHIdle clears the flag that indicates an SHIdle call is needed.  Next,
  142. //        SHIdle performs playback cleanup.  It iterates through the output records
  143. //        looking for records that are both in use and complete.  Such records are disposed
  144. //        with SHReleaseOutRec.  This frees the record for use later, and closes the sound
  145. //        channel with the Sound Manager.  Next, SHIdle performs recording cleanup.  It
  146. //        checks if recording is underway and is flagged as complete.  If so, it unlocks
  147. //        the (previously locked) input handle, and checks for errors.  If errors occurred,
  148. //        the input handle is disposed and the recordErr field of the input variable record
  149. //        is filled with the error.  This allows the error to later be reported to the
  150. //        caller when he calls SHGetRecordedSound.  If no error occured, then the sound
  151. //        header is re-written into the input sound handle, this time with the correct
  152. //        length, and the handle is sized down to match the actual number of samples that
  153. //        were recorded.  Then the sound input device is closed and the application is
  154. //        notified that recording is complete through his Boolean completion flag.
  155. //
  156. //=======================================================================================
  157. pascal void SHIdle(void)
  158. {
  159.     short    i;
  160.     OSErr    err;
  161.     long    realSize;
  162.     
  163.     // Immediately turn off the application's "Helper needs time" flag
  164.     *gsSHNeedsTime = false;
  165.  
  166.     // Do playback cleanup
  167.     for (i = 0; i < gsSHOutVars.numOutRecs; i++)
  168.         if (gsSHOutVars.outArray[i].inUse &&
  169.                 gsSHOutVars.outArray[i].channel.userInfo == kSHComplete)
  170.             // We've found a channel that needs closing...
  171.             SHReleaseOutRec(&gsSHOutVars.outArray[i]);
  172.     
  173.     // Do recording cleaunp
  174.     if (gsSHInVars.recording && gsSHInVars.recordComplete) {
  175.         HUnlock(gsSHInVars.inHandle);
  176.         
  177.         if (gsSHInVars.inPB.error && gsSHInVars.inPB.error != abortErr) {
  178.             // An error (other than a manual stop) occurred during recording.  Kill the
  179.             // handle and save the error code.
  180.             gsSHInVars.recordErr = gsSHInVars.inPB.error;
  181.             DisposeHandle(gsSHInVars.inHandle);
  182.             gsSHInVars.inHandle = nil;
  183.         } else {
  184.             // Recording completed normally (which includes abortErr, the "error" that
  185.             // occurs when recording is manually stopped).  We re-write the header (to
  186.             // slam the correct size in there), and size the handle to fit the actual
  187.             // recorded size (which either shortens the handle, or doesn't change its
  188.             // size -- that's why we don't bother checking the error).
  189.             gsSHInVars.recordErr = noErr;
  190.             realSize = gsSHInVars.inPB.count + gsSHInVars.headerLength;
  191.             err = SetupSndHeader((SndListHandle)gsSHInVars.inHandle, gsSHInVars.numChannels,
  192.                 gsSHInVars.sampleRate, gsSHInVars.sampleSize, gsSHInVars.compType,
  193.                 kSHBaseNote, realSize, &gsSHInVars.headerLength);
  194.             SetHandleSize(gsSHInVars.inHandle, realSize);        // Shorten the handle
  195.         }
  196.         
  197.         // Error or not, close the recording device, and notify the application that
  198.         // recording is complete, through the recording-completed flag that the caller
  199.         // originally passed into SHRecordStart.
  200.         SPBCloseDevice(gsSHInVars.inRefNum);
  201.         gsSHInVars.recording = false;
  202.         gsSHInVars.inRefNum = 0;
  203.         if (gsSHInVars.appComplete != nil)
  204.             *gsSHInVars.appComplete = true;
  205.     }
  206. }
  207.  
  208. //=======================================================================================
  209. //
  210. //    pascal void SHKillSoundHelper(void)
  211. //
  212. //    Summary:
  213. //        This routine terminates the Asynchronous Sound Helper.
  214. //
  215. //    Scope:
  216. //        Public.
  217. //
  218. //    Parameters:
  219. //        None.
  220. //
  221. //    Returns:
  222. //        Nothing.
  223. //
  224. //    Operation:
  225. //        This routine, after checking that the Helper was previously initialized, stops
  226. //        all current playback and recording, waits 1/60th of a second (to allow the Sound
  227. //        Manager to call our callback routines, SHPlayCompletion and SHRecordCompletion),
  228. //        then calls SHIdle to force cleanup (releasing sound channels and closing the
  229. //        sound input device if appropriate).  Then, SHKillSoundHelper disposes of the
  230. //        output records.
  231. //
  232. //=======================================================================================
  233. pascal void SHKillSoundHelper(void)
  234. {
  235.     short    i;
  236.     long    timeout;
  237.     Boolean    outputClean, inputClean;
  238.     
  239.     if (!gsSHInited)
  240.         return;
  241.  
  242.     SHPlayStopAll();    // Kill all playback
  243.     SHRecordStop();        // Kill recording
  244.     
  245.     // Now sync-wait for everything to clean itself up
  246.     timeout = TickCount() + kSHSyncWaitTimeout;
  247.     do {
  248.         if (*gsSHNeedsTime)
  249.             SHIdle();            // Clean up when required
  250.  
  251.         // Check if all our output channels are cleaned up
  252.         outputClean = true;
  253.         for (i = 0; i < gsSHOutVars.numOutRecs && outputClean; i++)
  254.             if (gsSHOutVars.outArray[i].inUse)
  255.                 outputClean = false;
  256.         
  257.         // Check whether our recording is cleaned up
  258.         inputClean = !gsSHInVars.recording;
  259.         
  260.         if (inputClean && outputClean)
  261.             break;
  262.     } while (TickCount() < timeout);
  263.     
  264.     // Lose our preallocated sound channels
  265.     DisposePtr((Ptr)gsSHOutVars.outArray);
  266. }
  267.  
  268. //=======================================================================================
  269. //
  270. //    long SHNewRefNum(void)
  271. //
  272. //    Summary:
  273. //        This routine returns the next available output reference number.
  274. //
  275. //    Scope:
  276. //        Private.
  277. //
  278. //    Parameters:
  279. //        None.
  280. //
  281. //    Returns:
  282. //        The next available output reference number.
  283. //
  284. //    Operation:
  285. //        The output variable nextRef contains the next available reference number.  This
  286. //        function returns the value of nextRef then increments it.  This way, output ref-
  287. //        erence numbers are unique throughout a session (modulo 2,147,483,647).
  288. //
  289. //=======================================================================================
  290. long SHNewRefNum(void)
  291. {
  292.     return(gsSHOutVars.nextRef++);
  293. }
  294.  
  295. //=======================================================================================
  296. //
  297. //    OSErr SHNewOutRec(SHOutPtr *outRec)
  298. //
  299. //    Summary:
  300. //        This routine attempts to return the first available output record.
  301. //
  302. //    Scope:
  303. //        Private.
  304. //
  305. //    Parameters:
  306. //        outRec        A pointer to an SHOutRecPtr.  This is where SHNewOutRec puts
  307. //                    the pointer to the output record.
  308. //
  309. //    Returns:
  310. //        kSHErrOutaChannels        If no free output record was found.
  311. //        noErr                    Otherwise.
  312. //
  313. //    Operation:
  314. //        SHNewOutRec simply iterates through the output record array looking for a record
  315. //        that is not in use.  If all the records are in use, SHNewOutRec returns the
  316. //        Helper error code kSHErrOutaChannels.  If a record is found, then the address of
  317. //        the record is stored via the VAR parameter, and noErr is returned.
  318. //
  319. //=======================================================================================
  320. OSErr SHNewOutRec(SHOutPtr *outRec)
  321. {
  322.     short    i;
  323.     
  324.     // First look for a free channel among our preallocated output records
  325.     for (i = 0; i < gsSHOutVars.numOutRecs; i++)
  326.         if (!gsSHOutVars.outArray[i].inUse) {
  327.             *outRec = &gsSHOutVars.outArray[i];
  328.             return(noErr);
  329.         }
  330.     
  331.     return(kSHErrOutaChannels);
  332. }
  333.  
  334. //=======================================================================================
  335. //
  336. //    pascal void SHPlayCompletion(SndChannelPtr channel, SndCommand *command)
  337. //
  338. //    Summary:
  339. //        This routine is the playback callback routine we provide to the Sound Manager.
  340. //
  341. //    Scope:
  342. //        Private.
  343. //
  344. //    Parameters:
  345. //        channel        A pointer to the sound channel that is calling back.  It is calling
  346. //                    back because we queued up a callbackCmd.
  347. //        command        A pointer to the actual command that caused us to be called back.
  348. //                    This command happens to have important information (like the app's
  349. //                    A5, and a constant we can use to verify that this is a "real" call-
  350. //                    back, as opposed to one that has been erroneously generated by the
  351. //                    Sound Manager).
  352. //
  353. //    Returns:
  354. //        Nothing.
  355. //
  356. //    Operation:
  357. //        This routine first looks for our "completion signature."  This is how we know
  358. //        that the callback really means the sound has completed.  There is a bug in the
  359. //        Sound Manager that may cause callbacks that weren't specifically requested, and
  360. //        this constant allows us to distinguish our "real" callback from one that is a
  361. //        result of that bug.  If the callback is hip, then we set up A5 (so we can ref-
  362. //        erence our globals), and set the application's attention flag.  Also, we set
  363. //        the channel's userInfo field to a constant that SHIdle will recognize, so SHIdle
  364. //        will know the sound on that channel has completed, and that the channel can be
  365. //        freed.
  366. //
  367. //=======================================================================================
  368. pascal void SHPlayCompletion(SndChannelPtr channel, SndCommand *command)
  369. {
  370.     // we only need this variable if not on PPC (BDM)
  371. #ifndef powerc
  372.     long    otherA5;
  373. #endif
  374.  
  375.     // Look for our "callback signature" in the sound command.
  376.     if (command->param1 == kSHCompleteSig) {
  377.     
  378. #ifndef powerc
  379.         otherA5 = SetA5(command->param2);    // Set up our A5 (only on 68K - BDM)
  380. #endif
  381.         channel->userInfo = kSHComplete;
  382.         *gsSHNeedsTime = true;                // Tell the app to give us an SHIdle call
  383.  
  384. #ifndef powerc
  385.         SetA5(otherA5);                        // Retore old A5 (only on 68K - BDM)
  386. #endif
  387.  
  388.     }
  389. }
  390.  
  391. //=======================================================================================
  392. //
  393. //    pascal void SHRecordCompletion(SPBPtr inParams)
  394. //
  395. //    Summary:
  396. //        This routine is the recording callback routine we provide to the Sound Manager.
  397. //
  398. //    Scope:
  399. //        Private.
  400. //
  401. //    Parameters:
  402. //        inParams        This points to the input sound parameter block that has completed
  403. //                        recording.  When we filled out the parameter block, we stuffed
  404. //                        the application A5 into userLong, which allows us to access our
  405. //                        globals while we're here.
  406. //
  407. //    Returns:
  408. //        Nothing.
  409. //
  410. //    Operation:
  411. //        When recording completes for any reason (error, consumed all the memory that was
  412. //        provided, abort, etc.) then the Sound Manager calls our callback routine.  This
  413. //        routine first grabs A5 from the SPB's userLong field and sets us up to use our
  414. //        globals.  Then, it sets the application's "Helper needs time" Boolean, and sets
  415. //        our internal flag that recording has completed (so SHIdle will know to close the
  416. //        recording device, etc.)
  417. //
  418. //=======================================================================================
  419. pascal void SHRecordCompletion(SPBPtr inParams)
  420. {
  421. #ifndef powerc
  422.     long    otherA5;
  423.     otherA5 = SetA5(inParams->userLong);        // Set up our A5 (only on 68K - BDM)
  424. #endif
  425.     
  426.     *gsSHNeedsTime = true;                        // Notify the app to give us time
  427.     gsSHInVars.recordComplete = true;            // Make a note to ourselves, too
  428.  
  429. #ifndef powerc
  430.     SetA5(otherA5);                                // Restore old A5 (only on 68K - BDM)
  431. #endif
  432. }
  433.  
  434. //=======================================================================================
  435. //
  436. //    OSErr SHInitOutRec(SHOutPtr outRec, long refNum, Handle sound, char handleState)
  437. //
  438. //    Summary:
  439. //        This routine is used to fill out an SHOutRec and call SndNewChannel.
  440. //
  441. //    Scope:
  442. //        Private.
  443. //
  444. //    Parameters:
  445. //        outRec            A pointer to the SHOutRec we're filling out.
  446. //        refNum            The output reference number we'll give back to the caller.
  447. //        sound            A locked, non-purgeable handle to a (hopefully) valid sound.
  448. //        handleState        The original handle state, before the HLock and HNoPurge.  This
  449. //                        allows SHReleaseOutRec to properly reset the handle's flags when
  450. //                        playback is complete.
  451. //
  452. //    Returns:
  453. //        OSErr            Error results of SndNewChannel call, if an error occurred.
  454. //        noErr            Otherwise.
  455. //
  456. //    Operation:
  457. //        This routine is called to fill out a SHOutRec.  First, it clears the SndChannel
  458. //        within the SHOutRec to all zeros.  It then installs the default queue size, and
  459. //        calls the Sound Manager routine SndNewChannel.  If an error occurs, we return
  460. //        it right away.  If not, then we fill out the rest of the fields in the SHOutRec.
  461. //        Note that in the call to SndNewChannel, we specify NO SYNTHESIZER, and NO INIT-
  462. //        IALIZATION.  This is because the Helper is a "sound service," and has no freakin'
  463. //        idea what kind of sound the caller is going to try to play using this channel.
  464. //        So, we assume nothing.  Also note that we provide our completion routine,
  465. //        SHPlayCompletion, to SndNewChannel.
  466. //
  467. //=======================================================================================
  468. OSErr SHInitOutRec(SHOutPtr outRec, long refNum, Handle sound, char handleState)
  469. {
  470.     short            i;
  471.     OSErr            err;
  472.     SndChannelPtr    channel;
  473.     
  474.     // Initialize the sound channel inside outRec.  We'll clear the bytes to zero,
  475.     // install the proper queue size, then call SndNewChannel.
  476.     for (i = 0; i < sizeof(SndChannel); i++)
  477.         ((char *)&outRec->channel)[i] = 0;
  478.     outRec->channel.qLength = stdQLength;
  479.     channel = &outRec->channel;
  480.     err = SndNewChannel(&channel, kSHNoSynth, kSHNoInit, gsSHPlayCompletionUPP); // changed to UPP (BDM)
  481.     if (err != noErr)
  482.         return(err);
  483.     
  484.     // Initialize the rest of the record and return noErr.  Note that we only set the
  485.     // record's inUse flag if the SndNewChannel call was successful.
  486.     outRec->refNum = refNum;
  487.     outRec->sound = sound;
  488.     outRec->rate = 0;
  489.     outRec->handleState = handleState;
  490.     outRec->inUse = true;
  491.     outRec->paused = false;
  492.     return(noErr);
  493. }
  494.  
  495. //=======================================================================================
  496. //
  497. //    void SHReleaseOutRec(SHOutPtr outRec)
  498. //
  499. //    Summary:
  500. //        This routine "releases," or frees up an output record.
  501. //
  502. //    Scope:
  503. //        Private.
  504. //
  505. //    Parameters:
  506. //        outRec        A pointer to the output record we want to release.
  507. //
  508. //    Returns:
  509. //        Nothing.
  510. //
  511. //    Operation:
  512. //        If the output record's inUse flag is set, that means that SndNewChannel has been
  513. //        called for it.  In that case, we call SndDisposeChannel to allow the Sound Man-
  514. //        ager to dispose it's internal data structures for this channel.  Either way, if
  515. //        there's an associated sound, we check whether the sound is playing on any other
  516. //        channel, and if not, we reset it's handle flags.  Finally, we clear the record's
  517. //        inUse flag, thereby allowing it to be reused.
  518. //
  519. //=======================================================================================
  520. void SHReleaseOutRec(SHOutPtr outRec)
  521. {
  522.     short    i;
  523.     Boolean    found = false;
  524.     
  525.     // An SHOutRec's inUse flag only gets set if SndNewChannel has been called on the
  526.     // record's sound channel.  So if it is in use, we try a call to SndDisposeChannel,
  527.     // and ignore the error (what else can we do?)
  528.     if (outRec->inUse)
  529.         SndDisposeChannel(&outRec->channel, kSHQuietNow);
  530.  
  531.     // If this sound handle isn't being used by some other output record, kindly restore
  532.     // the original handle state.
  533.     if (outRec->sound != nil) {
  534.         for (i = 0; i < gsSHOutVars.numOutRecs && !found; i++)
  535.             if (&gsSHOutVars.outArray[i] != outRec && gsSHOutVars.outArray[i].inUse &&
  536.                     gsSHOutVars.outArray[i].sound == outRec->sound)
  537.                 found = true;
  538.         
  539.         if (!found)
  540.             HSetState(outRec->sound,outRec->handleState);
  541.     }
  542.     
  543.     outRec->inUse = false;
  544. }
  545.  
  546. //=======================================================================================
  547. //
  548. //    OSErr SHQueueCallback(SndChannel *channel)
  549. //
  550. //    Summary:
  551. //        This routine queues up a verifyable callback in the given sound channel.
  552. //
  553. //    Scope:
  554. //        Private:
  555. //
  556. //    Parameters:
  557. //        channel        The sound channel we want a callback from.
  558. //
  559. //    Returns:
  560. //        OSErr        Results of the SndDoCommand call.
  561. //
  562. //    Operation:
  563. //        This routine queues up a sound command in the given channel that will cause a
  564. //        callback.  This is how we'll know the sound completed.  In order to make the
  565. //        callback verifyable, we stuff kSHCompleteSig into param1.  We can test for this
  566. //        value within the callback routine.  Also, since the poor callback is called at
  567. //        interrupt time and can't count on its A5 world, we provide the application A5
  568. //        in param2.
  569. //
  570. //=======================================================================================
  571. OSErr SHQueueCallback(SndChannel *channel)
  572. {
  573.     SndCommand    command;
  574.  
  575.     command.cmd = callBackCmd;
  576.     command.param1 = kSHCompleteSig;        // To make the callback verifyable.
  577.  
  578. #ifdef powerc
  579.     command.param2 = 0L;                    // Put a zero there if we don't need A5 (BDM)
  580. #else
  581.     command.param2 = SetCurrentA5();        // So the callback routine can access globals.
  582. #endif
  583.  
  584.     return(SndDoCommand(channel, &command, kSHWait));
  585. }
  586.  
  587. //=======================================================================================
  588. //
  589. //    OSErr SHBeginPlayback(SHOutPtr outRec)
  590. //
  591. //    Summary:
  592. //        This routine begins playback of the sound that's installed in the given SHOutRec.
  593. //
  594. //    Scope:
  595. //        Private.
  596. //
  597. //    Parameters:
  598. //        outRec        The output record that we want to start playback on.
  599. //
  600. //    Returns:
  601. //        OSErr        Error results of SndPlay, if an error occurred.
  602. //        noErr        Otherwise.
  603. //
  604. //    Operation:
  605. //        This routine calls SndPlay (asynchronously) for the sound that is installed in
  606. //        the given output record.  This begins playback.  Immediately following that, we
  607. //        queue up a callback, so that when the sound that we just start completes, we'll
  608. //        get a callback (to SHPlayCompletion), and we'll know that the channel needs to
  609. //        be released.
  610. //
  611. //    IMPORTANT:
  612. //        This routine is called from SHPlayByID, and SHPlayByHandle when a sound handle is
  613. //        provided.  The purpose of these routines is to trigger a sound, and if you call
  614. //        SHPlayByID or SHPlayByHandle that way, DON'T use SHGetChannel to get the sound
  615. //        channel Helper is using to play the sound, then subsequently call SndPlay your-
  616. //        self to play some other sound.  Why not?  There is a bug in pre-7.0 Systems that
  617. //        causes a crash if more than one SndPlay call is made on the same channel.  Helper
  618. //        will never do this on its own, and you shouldn't either. If you want a sound
  619. //        channel that you want to send commands to, call SHPlayByHandle with a nil handle,
  620. //        then call SHGetChannel to retreive a pointer to the channel.
  621. //
  622. //=======================================================================================
  623. OSErr SHBeginPlayback(SHOutPtr outRec)
  624. {
  625.     OSErr    err;
  626.     
  627.     // First, initiate playback.  If an error occurs, return it immediately.
  628.     err = SndPlay(&outRec->channel, (SndListHandle)outRec->sound, kSHAsync);
  629.     if (err != noErr)
  630.         return(err);
  631.     
  632.     // Playback started okay.  Let's queue up a callback command so we'll know when
  633.     // the sound is finished.
  634.     SHQueueCallback(&outRec->channel);        // ignore error (what can we do?)
  635.     return(noErr);
  636. }
  637.  
  638. //=======================================================================================
  639. //
  640. //    char SHGetState(Handle snd)
  641. //
  642. //    Summary:
  643. //        This routine is a local replacement for HGetState which tries to find snd in an
  644. //        existing output record.
  645. //
  646. //    Scope:
  647. //        Private:
  648. //
  649. //    Parameters:
  650. //        snd            The handle we want the handle state for
  651. //
  652. //    Returns:
  653. //        A char representing the handle flags (either currently or from some existing
  654. //        output record).
  655. //
  656. //    Operation:
  657. //        This routine searches the output record array for an output record that is both
  658. //        in use AND has a "sound" field equal to the parameter snd.  What this means is
  659. //        that we've found an output record that is currently playing snd.  If we find
  660. //        such a record, we return the "handleState" field of that output record.  If no
  661. //        such record is found, then we return the results of HGetState(snd).  The reason
  662. //        we need this is that you could re-trigger a sound (that is, play the same sound
  663. //        simultaneously on more than one Helper output channel).  In such a case, the
  664. //        first SHPlayByID or (ByHandle) call would get the actual handle state (from
  665. //        HGetState).  If another SHPlayByID call came in while the original was still
  666. //        playing, the handleState from the existing output record would be retured.  This
  667. //        way, the second Play doesn't save a "locked" state and restore to a locked state
  668. //        when the sound has completed.
  669. //
  670. //=======================================================================================
  671. char SHGetState(Handle snd)
  672. {
  673.     short    i;
  674.     
  675.     // Look for an output record that has snd for a sound.  If one is found, grab and
  676.     // return its handleState instead of the handle's current flags.
  677.     for (i = 0; i < gsSHOutVars.numOutRecs; i++)
  678.         if (gsSHOutVars.outArray[i].inUse && gsSHOutVars.outArray[i].sound == snd)
  679.             return(gsSHOutVars.outArray[i].handleState);
  680.     
  681.     return(HGetState(snd));
  682. }
  683.  
  684. //=======================================================================================
  685. //
  686. //    pascal OSErr SHPlayByID(short resID, long *refNum)
  687. //
  688. //    Summary:
  689. //        This routine begins asynchronous playback of the 'snd ' resource with ID resID.
  690. //
  691. //    Scope:
  692. //        Public.
  693. //
  694. //    Parameters:
  695. //        resID        The resource ID of the 'snd ' resource the caller wants to play back.
  696. //        refNum        A pointer to where to store the output reference number.  THIS IS
  697. //                    OPTIONAL.  If nil is specified, the refNum is not returned.
  698. //
  699. //    Returns:
  700. //        ResError()                If the GetResource failed, and ResError gave an error.
  701. //        resNotFound                If the GetResource failed, but ResError was noErr.
  702. //        kSHErrOutaChannels        If the SHNewOutRec call failed.
  703. //        OSErr                    If the SHInitOutRec call failed.
  704. //        OSErr                    If the SHBeginPlayback call failed.
  705. //        noErr                    Otherwise.
  706. //
  707. //    Operation:
  708. //        This routine plays the 'snd ' resource referred to by ID resID.  First, we try
  709. //        to load the sound resource.  If successful, we note the handle state of the
  710. //        sound handle, and set it to be nonpurgeable (because we don't want subsequent
  711. //        operations, namely the SndNewChannel call that happens in SHInitOutRec, to wipe
  712. //        out the sound we so carefully read into memory).  Then we get a reference number
  713. //        and a pointer to the next free output record.  If successful, we initialize the
  714. //        output record (and open the sound channel) with SHInitOutRec.  If successful,
  715. //        we move the sound handle as high in the heap as we can (to help avoid fragmen-
  716. //        tation), and lock it.  Then we call SHBeginPlayback to start the sound playing
  717. //        and queue up a callback so we'll know when it's done.  If successful, we return
  718. //        the reference number (if the caller wants it).
  719. //
  720. //    IMPORTANT:
  721. //        DO NOT start a sound playing with SHPlayByID, get it's channel with SHGetChannel,
  722. //        and then do another SndPlay on that channel!  This will crash on pre-7.0 systems.
  723. //        If you want a channel, use SHPlayByHandle with a nil handle.  See the comments
  724. //        for SHPlayByHandle and SHBeginPlayback.
  725. //        
  726. //=======================================================================================
  727. pascal OSErr SHPlayByID(short resID, long *refNum)
  728. {
  729.     Handle        sound;
  730.     char        oldhandleState;
  731.     short        ref;
  732.     OSErr        err;
  733.     SHOutPtr    outRec;
  734.     
  735.     // First, try to get the caller's 'snd ' resource.  If we can't return ResError().
  736.     // If we DO get the sound, save it's flags, then set it to be nonpurgeable.  This
  737.     // is because some of the Sound Manager stuff below may cause memory allocation,
  738.     // which could cause the sound to be purged.  We don't want that, since we're
  739.     // going to start playing it real soon.
  740.     sound = GetResource(soundListRsrc, resID);
  741.     if (sound == nil) {
  742.         err = ResError();
  743.         if (err == noErr)
  744.             err = resNotFound;
  745.         return(err);
  746.     }
  747.     oldhandleState = SHGetState(sound);
  748.     HNoPurge(sound);
  749.         
  750.     // Now let's get a reference number and an output record.
  751.     ref = SHNewRefNum();
  752.     err = SHNewOutRec(&outRec);
  753.     if (err != noErr) {
  754.         HSetState(sound, oldhandleState);
  755.         return(err);
  756.     }
  757.     
  758.     // Now let's fill in the output record with all the pertinent information.  This
  759.     // routine also initializes the sound channel and flags outRec as "in use."
  760.     err = SHInitOutRec(outRec, ref, sound, oldhandleState);
  761.     if (err != noErr) {
  762.         HSetState(sound, oldhandleState);
  763.         SHReleaseOutRec(outRec);
  764.         return(err);
  765.     }
  766.     
  767.     // At this point, we're in pretty good shape.  We've got a reference number, an
  768.     // initialized output record, and the sound handle.  Let's party.
  769.     MoveHHi(sound);
  770.     HLock(sound);
  771.     err = SHBeginPlayback(outRec);
  772.     if (err != noErr) {
  773.         HSetState(sound, oldhandleState);
  774.         SHReleaseOutRec(outRec);
  775.         return(err);
  776.     } else {
  777.         if (refNum != nil)            // refNum is optional -- the caller may not want it
  778.             *refNum = ref;
  779.         return(noErr);
  780.     }
  781. }
  782.  
  783. //=======================================================================================
  784. //
  785. //    pascal OSErr SHPlayByHandle(Handle sound, long *refNum)
  786. //
  787. //    Summary:
  788. //        This routine begins asynchronous playback of a sound provided in a handle.
  789. //
  790. //    Scope:
  791. //        Public.
  792. //
  793. //    Parameters:
  794. //        sound        A handle to the sound the caller wants to play.  This may optionally
  795. //                    be nil, indicating that the sound channel should be opened, but no
  796. //                    SndPlay call should be made.  If a caller does this, he usually calls
  797. //                    SHGetChannel to get a pointer to the sound channel, so he can send
  798. //                    sound commands to the channel.
  799. //        refNum        A pointer to where to store the output reference number.  THIS IS
  800. //                    OPTIONAL.  If nil is passed, the reference number is not returned.
  801. //
  802. //    Returns:
  803. //        kSHErrOutaChannels        If the SHNewOutRec call failed.
  804. //        OSErr                    If the SHInitOutRec call failed.
  805. //        OSErr                    If the SHBeginPlayback call failed.
  806. //        noErr                    Otherwise.
  807. //
  808. //    Operation:
  809. //        If a handle is provided, we set it to be nonpurgeable so that subsequent oper-
  810. //        ations don't blow it away, and we note it's current handle state.  Then, we get
  811. //        a reference number and a pointer to a free output record.  If successful, we
  812. //        initialize the output record, thereby opening the sound channel.  Then, if a
  813. //        sound was provided, we move it high, lock it, and call SHBeginPlayback to begin
  814. //        asynchronous playback and queue up a callback.  Finally, we return the reference
  815. //        number if the caller wants it.  If the sound wasn't provided (i.e. nil), every-
  816. //        thing is the same except there's no SHBeginPlayback call.
  817. //
  818. //    IMPORTANT:
  819. //        DO NOT start a sound handle playing with SHPlayByHandle, get it's channel with
  820. //        SHGetChannel, and then do another SndPlay on that channel!  This will crash on
  821. //        pre-7.0 systems. If you want a channel, use SHPlayByHandle with a _NIL_ handle.
  822. //        See the comments for SHBeginPlayback.
  823. //        
  824. //=======================================================================================
  825. pascal OSErr SHPlayByHandle(Handle sound, long *refNum)
  826. {
  827.     char        oldhandleState;
  828.     short        ref;
  829.     OSErr        err;
  830.     SHOutPtr    outRec;
  831.     
  832.     // Save sound handle's flags, then set it to be nonpurgeable.  This is because some
  833.     // of the Sound Manager stuff below may cause memory allocation, which could cause
  834.     // the handle to be purged.  We don't want that, since we're going to start playing
  835.     // it real soon.  If the caller gave us nil for a sound handle, that means he's
  836.     // really just interested in having the sound channel.  So, we go on our merry way
  837.     // without a sound handle.
  838.     if (sound != nil) {
  839.         oldhandleState = SHGetState(sound);
  840.         HNoPurge(sound);
  841.     } else oldhandleState = 0;
  842.         
  843.     // Now, let's get a reference number and an output record.
  844.     ref = SHNewRefNum();
  845.     err = SHNewOutRec(&outRec);
  846.     if (err != noErr) {
  847.         if (sound != nil)
  848.             HSetState(sound, oldhandleState);
  849.         return(err);
  850.     }
  851.     
  852.     // Now let's fill in the output record with all the pertinent information.  This
  853.     // routine also initializes the sound channel and flags outRec as "in use."
  854.     err = SHInitOutRec(outRec, ref, sound, oldhandleState);
  855.     if (err != noErr) {
  856.         if (sound != nil)
  857.             HSetState(sound, oldhandleState);
  858.         SHReleaseOutRec(outRec);
  859.         return(err);
  860.     }
  861.     
  862.     // At this point, we're in pretty good shape.  We've got a reference number, an
  863.     // initialized output record, and the sound handle.  Let's get whacky.
  864.     if (sound != nil) {            // if we've got a sound, lock and begin playback
  865.         MoveHHi(sound);
  866.         HLock(sound);
  867.         err = SHBeginPlayback(outRec);
  868.         if (err != noErr) {
  869.             HSetState(sound, oldhandleState);
  870.             SHReleaseOutRec(outRec);
  871.             return(err);
  872.         } else {
  873.             if (refNum != nil)        // refNum is optional -  the caller may not want it
  874.                 *refNum = ref;
  875.             return(noErr);
  876.         }
  877.     } else {                        // if there's no sound, go ahead and return noErr
  878.         if (refNum != nil)            // refNum is optional -  the caller may not want it
  879.             *refNum = ref;
  880.         return(noErr);
  881.     }
  882. }
  883.  
  884. //=======================================================================================
  885. //
  886. //    SHOutPtr SHOutRecFromRefNum(long refNum)
  887. //
  888. //    Summary:
  889. //        This routine finds that SHOutRec that is associated with a given refNum, if any.
  890. //
  891. //    Scope:
  892. //        Private.
  893. //
  894. //    Parameters:
  895. //        refNum        The output reference number in question.
  896. //
  897. //    Returns:
  898. //        A pointer to the associated SHOutRec, if any, or nil, if none was found with a
  899. //        reference number matching refNum.
  900. //
  901. //    Operation:
  902. //        This handy routine searches the output record array looking for an output record
  903. //        that has the given reference number.  If one is found, a pointer to it is
  904. //        returned.  If not, then nil is returned.
  905. //
  906. //=======================================================================================
  907. SHOutPtr SHOutRecFromRefNum(long refNum)
  908. {
  909.     short    i;
  910.     
  911.     // Search for the specified refNum
  912.     for (i = 0; i < gsSHOutVars.numOutRecs; i++)
  913.         if (gsSHOutVars.outArray[i].inUse && gsSHOutVars.outArray[i].refNum == refNum)
  914.             break;
  915.     
  916.     // If we found it, return a pointer to that record, otherwise, nil.
  917.     if (i == gsSHOutVars.numOutRecs)
  918.         return(nil);
  919.     else return(&gsSHOutVars.outArray[i]);
  920. }
  921.  
  922. //=======================================================================================
  923. //
  924. //    void SHPlayStopByRec(SHOutPtr outRec)
  925. //
  926. //    Summary:
  927. //        This routine stops sound playback on the channel associated with the given
  928. //        output record.
  929. //
  930. //    Scope:
  931. //        Private.
  932. //
  933. //    Parameters:
  934. //        outRec        A pointer to the output record whose sound should be stopped.
  935. //
  936. //    Returns:
  937. //        Nothing.
  938. //
  939. //    Operation:
  940. //        This routine sends two immediate sound commands to the channel in the given
  941. //        output record.  The flushCmd gets rid of any unprocessed commands from the
  942. //        queue subsequent to the one currently being processed.  The quietCmd, when sent
  943. //        with SndDoImmediate, immediately quiets the channel.  Note that there might not
  944. //        be any synthesizer yet associate with this channel, and in that case, these
  945. //        commands are just eaten by the Sound Manager.
  946. //
  947. //=======================================================================================
  948. void SHPlayStopByRec(SHOutPtr outRec)
  949. {
  950.     SndCommand    cmd;
  951.  
  952.     // Dump the rest of the commands in the queue (including our callbackCmd).
  953.     cmd.cmd = flushCmd;
  954.     cmd.param1 = 0;
  955.     cmd.param2 = 0;
  956.     SndDoImmediate(&outRec->channel, &cmd);
  957.     
  958.     // Shut up this minute!  Go to your room!  No dessert tonite for you, little boy.
  959.     cmd.cmd = quietCmd;
  960.     cmd.param1 = 0;
  961.     cmd.param2 = 0;
  962.     SndDoImmediate(&outRec->channel, &cmd);
  963.     
  964.     // It is now safe to just manually dump our channel (we'll just skip the whole
  965.     // callback thing in this case).
  966.     SHReleaseOutRec(outRec);
  967. }
  968.  
  969. //=======================================================================================
  970. //
  971. //    pascal OSErr SHPlayStop(long refNum)
  972. //
  973. //    Summary:
  974. //        This routine stops playback on the output record referred to by refNum.
  975. //
  976. //    Scope:
  977. //        Public.
  978. //
  979. //    Parameters:
  980. //        refNum        The output reference number of the sound the caller wants stopped.
  981. //
  982. //    Returns:
  983. //        kSHErrBadRefNum        If the reference number does not refer to any current output
  984. //                            record.  (Note that this is not necessarily bad.  If they
  985. //                            try to stop a sound that has already stopped by its own
  986. //                            accord, this error will be returned.  Usually you can call
  987. //                            this routine and ignore the error.)
  988. //        noErr                Otherwise.
  989. //
  990. //    Operation:
  991. //        This routine calls SHOutRecFromRefNum to try to find the output record that is
  992. //        associated with refNum.  If one is found, we call SHPlayStopByRec to stop
  993. //        playback for that output record.
  994. //
  995. //=======================================================================================
  996. pascal OSErr SHPlayStop(long refNum)
  997. {
  998.     SHOutPtr    outRec;
  999.     
  1000.     // Look for the associated output record.
  1001.     outRec = SHOutRecFromRefNum(refNum);
  1002.     
  1003.     // If we found it, call SHPlayStopByRec to stop playback.
  1004.     if (outRec != nil) {
  1005.         SHPlayStopByRec(outRec);
  1006.         return(noErr);
  1007.     } else return(kSHErrBadRefNum);
  1008. }
  1009.  
  1010. //=======================================================================================
  1011. //
  1012. //    pascal OSErr SHPlayStopAll(void)
  1013. //
  1014. //    Summary:
  1015. //        This routine stops all sound that the Helper initiated.
  1016. //
  1017. //    Scope:
  1018. //        Public.
  1019. //
  1020. //    Parameters:
  1021. //        None.
  1022. //
  1023. //    Returns:
  1024. //        noErr        This may return something more interesting in the future.
  1025. //
  1026. //    Operation:
  1027. //        This routine iterates through all the output records looking for records that
  1028. //        are in use.  When an in-use record is found, playback on that record is stopped
  1029. //        by calling SHPlayStopByRec.  Errors are ignored.
  1030. //
  1031. //=======================================================================================
  1032. pascal OSErr SHPlayStopAll(void)
  1033. {
  1034.     short    i;
  1035.     
  1036.     // Look for output records that are in use and stop their playback with
  1037.     // SHPlayStopByRec.
  1038.     for (i = 0; i < gsSHOutVars.numOutRecs; i++)
  1039.         if (gsSHOutVars.outArray[i].inUse)
  1040.             SHPlayStopByRec(&gsSHOutVars.outArray[i]);
  1041.     
  1042.     return(noErr);
  1043. }
  1044.  
  1045. //=======================================================================================
  1046. //
  1047. //    pascal OSErr SHPlayPause(long refNum)
  1048. //
  1049. //    Summary:
  1050. //        This routine pauses playback of sound associated with refNum.
  1051. //
  1052. //    Scope:
  1053. //        Public.
  1054. //
  1055. //    Parameters:
  1056. //        refNum        The output reference number of the sound the caller wants paused.
  1057. //
  1058. //    Returns:
  1059. //        OSErr                If a SndDoImmediate fails.
  1060. //        kSHErrBadRefNum        If the given reference number is not associated with any
  1061. //                            current output record.
  1062. //        kSHErrAlreadyPaused    If the sound is already paused.
  1063. //
  1064. //    Operation:
  1065. //        For a sound like "Simple beep," which is a long sequence of sound commands,
  1066. //        pausing a sound means "pausing sound command queue processing," and is performed
  1067. //        with the pauseCmd.  Sampled sounds, like "Wild Eep," usually consist of a single
  1068. //        bufferCmd to play back the sampled sound.  The pauseCmd is ineffective with
  1069. //        sampled sounds because the sound is paused after the current command is processed
  1070. //        (the bufferCmd), so the entire sound would be played.  This is rarely what the
  1071. //        caller wants.  So, we've got a little trick in here to pause sampled sounds.  If
  1072. //        you set a sampled sound's sample playback rate to zero, it effectively pauses the
  1073. //        sampled sound in its tracks, mid-bufferCmd (which is what the caller probably
  1074. //        wants).
  1075. //
  1076. //        There is really no officially sanctioned way to know whether a sound is command-
  1077. //        type or sampled without parsing the sound.  However, any synthesizer that returns
  1078. //        a non-zero rate from a getRateCmd call will be able to understand a rateCmd.  So
  1079. //        we try a getRateCmd, and if we get a non-zero rate, send a rateCmd to set the
  1080. //        playback rate to zero.  If we get zero from the getRateCmd, we assume that the
  1081. //        synthesizer cannot understand the getRateCmd, and instead use a pauseCmd to pause
  1082. //        the sound.  This is the only offically sanctioned universal method of pausing any
  1083. //        sound.
  1084. //
  1085. //        If the sound was successfully paused, the output record's paused flag is set.
  1086. //
  1087. //=======================================================================================
  1088. pascal OSErr SHPlayPause(long refNum)
  1089. {
  1090.     SHOutPtr    outRec;
  1091.     SndCommand    cmd;
  1092.     OSErr        err;
  1093.     
  1094.     outRec = SHOutRecFromRefNum(refNum);
  1095.     if (outRec != nil) {
  1096.         // Don't bother with this if we're already paused.
  1097.         if (outRec->paused)
  1098.             return(kSHErrAlreadyPaused);
  1099.         
  1100.         // Get the current playback rate for this sound.
  1101.         cmd.cmd = getRateCmd;
  1102.         cmd.param1 = 0;
  1103.         cmd.param2 = (long)&outRec->rate;
  1104.         err = SndDoImmediate(&outRec->channel, &cmd);
  1105.         if (err != noErr)
  1106.             return(err);
  1107.         
  1108.         // Now pause with either a rateCmd or a pauseCmd, as appropriate
  1109.         cmd.param1 = 0;
  1110.         cmd.param2 = 0;
  1111.         if (outRec->rate != 0) {
  1112.             // If we get something non-zero, it's safe to assume that whatever
  1113.             // synthesizer we're talking to will be able to understand a rateCmd to
  1114.             // restore the rate (probably the sampled synthesizer).  To pause the
  1115.             // sound, we'll set the rate to zero.
  1116.             cmd.cmd = rateCmd;
  1117.             err = SndDoImmediate(&outRec->channel, &cmd);
  1118.             if (err != noErr)
  1119.                 return(err);
  1120.         } else {
  1121.             // This synthesizer doesn't understand rateCmds.  So instead we'll just
  1122.             // pause command queue processing with a pauseCmd.  This is how command-type
  1123.             // sounds (e.g. Simple Beep) are paused.
  1124.             cmd.cmd = pauseCmd;
  1125.             err = SndDoImmediate(&outRec->channel, &cmd);
  1126.             if (err != noErr)
  1127.                 return(err);
  1128.         }
  1129.  
  1130.         outRec->paused = true;
  1131.         return(noErr);
  1132.     } else return(kSHErrBadRefNum);
  1133. }
  1134.  
  1135. //=======================================================================================
  1136. //
  1137. //    pascal OSErr SHPlayContinue(long refNum)
  1138. //
  1139. //    Summary:
  1140. //        This routine continues playback of a previously paused sound.
  1141. //
  1142. //    Scope:
  1143. //        Public.
  1144. //
  1145. //    Parameters:
  1146. //        refNum        The refNum of the sound the caller wants playback continued on.  This
  1147. //                    should be the refNum of a sound that was previously paused with
  1148. //                    SHPlayPause.
  1149. //
  1150. //    Returns:
  1151. //        OSErr                    If the SndDoImmediate fails.
  1152. //        kSHErrBadRefNum            If the refNum doesn't refer to any current output record.
  1153. //        kSHErrAlreadyContinued    If the sound is not paused.
  1154. //
  1155. //    Operation:
  1156. //        First SHPlayContinue gets a pointer to the output record (if any) that refNum
  1157. //        refers to.  If found, and that sound is paused, we check the output record's
  1158. //        rate field.  If non-zero, then the sound was paused with rateCmd, so we send 
  1159. //        another rateCmd to restore its playback rate.  Otherwise, we send a resumeCmd
  1160. //        (to resume command-queue processing).  (See the comments for SHPlayPause for
  1161. //        details on the two methods of pausing sound.)  If the resumeCmd is successful,
  1162. //        we clear the output record's paused flag.
  1163. //
  1164. //=======================================================================================
  1165. pascal OSErr SHPlayContinue(long refNum)
  1166. {
  1167.     SHOutPtr    outRec;
  1168.     SndCommand    cmd;
  1169.     OSErr        err;
  1170.     
  1171.     outRec = SHOutRecFromRefNum(refNum);
  1172.     if (outRec != nil) {
  1173.         // Don't even bother with this stuff if the channel isn't paused.
  1174.         if (!outRec->paused)
  1175.             return(kSHErrAlreadyContinued);
  1176.         
  1177.         // Now continue playback with a rateCmd or a resumeCmd, as appropriate.
  1178.         cmd.param1 = 0;
  1179.         if (outRec->rate != 0) {
  1180.             // Resume sampled sound playback by restoring the synthesizer's playback
  1181.             // rate with a rateCmd.
  1182.             cmd.cmd = rateCmd;
  1183.             cmd.param2 = outRec->rate;
  1184.             err = SndDoImmediate(&outRec->channel, &cmd);
  1185.             if (err != noErr)
  1186.                 return(err);
  1187.         } else {
  1188.             // Resume sound queue processing with a resumeCmd.
  1189.             cmd.cmd = resumeCmd;
  1190.             cmd.param2 = 0;
  1191.             err = SndDoImmediate(&outRec->channel, &cmd);
  1192.             if (err != noErr)
  1193.                 return(err);
  1194.         }
  1195.         
  1196.         outRec->paused = false;
  1197.         return(noErr);
  1198.     } else return(kSHErrBadRefNum);
  1199. }
  1200.  
  1201. //=======================================================================================
  1202. //
  1203. //    pascal SHPlayStat SHPlayStatus(long refNum)
  1204. //
  1205. //    Summary:
  1206. //        This routine returns a status value for the sound associated with refNum.
  1207. //
  1208. //    Scope:
  1209. //        Public.
  1210. //
  1211. //    Parameters:
  1212. //        refNum        The sound for which the caller wishes status information.
  1213. //
  1214. //    Returns:
  1215. //        shpError = -1        If refNum has never been used (and is therefore invalid).
  1216. //        shpFinished = 0        If the sound associated with refNum has completed.
  1217. //        shpPaused = 1        If the sound associated with refNum is currently paused.
  1218. //        shpPlaying = 2        If the sound associated with refNum is currently playing.
  1219. //
  1220. //    Operation:
  1221. //        First we check to see if refNum is greater than or equal to our next output
  1222. //        reference number.  If it is, then this reference number is definitely invalid,
  1223. //        so we return shpError.  Otherwise, we look refNum up with SHOutRecFromRefNum.
  1224. //        If no record is found (but we know that refNum has been used in the past), we
  1225. //        can assume that the sound has completed, and return shpFinished.  Otherwise,
  1226. //        the sound is currently playing or is paused, so we return either shpPaused or
  1227. //        shpPlaying based on the value of the output record's paused flag.
  1228. //
  1229. //=======================================================================================
  1230. pascal SHPlayStat SHPlayStatus(long refNum)
  1231. {
  1232.     SHOutPtr    outRec;
  1233.     
  1234.     if (refNum >= gsSHOutVars.nextRef)
  1235.         return(shpError);
  1236.     else {
  1237.         outRec = SHOutRecFromRefNum(refNum);
  1238.     
  1239.         if (outRec != nil) {
  1240.             // We found an SHOutRec for the guy's ref num, (so it's in use).
  1241.             return((outRec->paused) ? shpPaused : shpPlaying);
  1242.         } else {
  1243.             // Although we've used the reference number in the past, it's not in use, so
  1244.             // we can assume whatever sound it was associated has since stopped.  So,
  1245.             // we'll return shpFinished in this case.
  1246.             return(shpFinished);
  1247.         }
  1248.     }
  1249. }
  1250.  
  1251. //=======================================================================================
  1252. //
  1253. //    pascal OSErr SHGetChannel(long refNum, SndChannelPtr *channel)
  1254. //
  1255. //    Summary:
  1256. //        This routine allows the caller to retrieve a pointer to the sound channel that
  1257. //        is associated with the given refNum.
  1258. //
  1259. //    Scope:
  1260. //        Public.
  1261. //
  1262. //    Parameters:
  1263. //        refNum        The sound for which the caller wants to retrieve a sound channel
  1264. //                    pointer.
  1265. //        channel        A pointer to a SndChannelPtr.  This VAR parameter is where the sound
  1266. //                    channel address is stored if it is found.
  1267. //
  1268. //    Returns:
  1269. //        kSHErrBadRefNum        If refNum doesn't refer to any current output record.
  1270. //        noErr                Otherwise.
  1271. //
  1272. //    Operation:
  1273. //        This routine is provided to allow more advanced callers to have access to the
  1274. //        sound channel associated with a reference number.  This could be useful, for
  1275. //        instance, if the caller wanted to send sound commands to the channel, and only
  1276. //        use the Helper to manage the channel (but not the sound).  A good example of
  1277. //        this is continuous background music.  You make a sound with a soundCmd and a
  1278. //        loop.  Then you open a channel by doing a SHPlayByHandle(nil, &ref), then get
  1279. //        the channel pointer by calling SHGetChannel, manually PlaySnd the background
  1280. //        music sound (which should contain a soundCmd to install the music as a voice),
  1281. //        then send a freqCmd to start the music playing.  It'll keep looping until a
  1282. //        quietCmd comes along.  (SEE NOTE BELOW.)
  1283. //
  1284. //    IMPORTANT:
  1285. //        If you use the above-described technique to provide looped background sound, it
  1286. //        is important to note that when you change the background music (e.g. from one
  1287. //        song to the next), you should SHPlayStop the channel, and allocate a new channel
  1288. //        with new calls to SHPlayByHandle(nil, &ref)/SHGetChannel.  DO NOT make another
  1289. //        SndPlay call on the same channel to change the sound, because this will crash on
  1290. //        pre-7.0 Systems.
  1291. //
  1292. //=======================================================================================
  1293. pascal OSErr SHGetChannel(long refNum, SndChannelPtr *channel)
  1294. {
  1295.     SHOutPtr    outRec;
  1296.     
  1297.     // Look for the output record associated with refNum.
  1298.     outRec = SHOutRecFromRefNum(refNum);
  1299.     
  1300.     // If we found one, return a pointer to the sound channel.
  1301.     if (outRec != nil) {
  1302.         *channel = &outRec->channel;
  1303.         return(noErr);
  1304.     } else return(kSHErrBadRefNum);
  1305. }
  1306.  
  1307. //=======================================================================================
  1308. //
  1309. //    OSErr SHGetDeviceSettings(long inRefNum, short *numChannels, Fixed *sampleRate,
  1310. //        short *sampleSize, OSType *compType)
  1311. //
  1312. //    Summary:
  1313. //        This routine gets several parameters from an open sound input device.
  1314. //
  1315. //    Scope:
  1316. //        Private.
  1317. //
  1318. //    Parameters:
  1319. //        inRefNum        The sound input device's input reference number.
  1320. //        numChannels        A VAR parameter in which the number of channels is returned.
  1321. //        sampleRate        A VAR parameter in which the sample rate (in Hz) is returned.
  1322. //        sampleSize        A VAR parameter in which the number of bits/sample is returned.
  1323. //        compType        A VAR parameter in which the compression type is returned.
  1324. //
  1325. //    Returns:
  1326. //        OSErr            If any of the SPBGetDeviceInfo calls fail.
  1327. //        noErr            Otherwise.
  1328. //
  1329. //    Operation:
  1330. //        This routine does four SPBGetDeviceInfo calls to retrieve the number of channels
  1331. //        the sample rate, the sample size, and the compression type for the input device
  1332. //        referred to by inRefNum.  This routine is almost verbatim out of Inside Macintosh
  1333. //        Volume 6.
  1334. //
  1335. //=======================================================================================
  1336. OSErr SHGetDeviceSettings(long inRefNum, short *numChannels, Fixed *sampleRate,
  1337.     short *sampleSize, OSType *compType)
  1338. {
  1339.     OSErr    err;
  1340.     
  1341.     // Hit on that sound input device.
  1342.     err = SPBGetDeviceInfo(inRefNum, siNumberChannels, (Ptr)numChannels);
  1343.     if (err != noErr)
  1344.         return(err);
  1345.     err = SPBGetDeviceInfo(inRefNum, siSampleRate, (Ptr)sampleRate);
  1346.     if (err != noErr)
  1347.         return(err);
  1348.     err = SPBGetDeviceInfo(inRefNum, siSampleSize, (Ptr)sampleSize);
  1349.     if (err != noErr)
  1350.         return(err);
  1351.     err = SPBGetDeviceInfo(inRefNum, siCompressionType, (Ptr)compType);
  1352.     return(err);
  1353. }
  1354.  
  1355. //=======================================================================================
  1356. //
  1357. //    pascal OSErr SHRecordStart(short maxK, OSType quality, Boolean *doneFlag)
  1358. //
  1359. //    Summary:
  1360. //        This routine initiates asynchronous sound recording.
  1361. //
  1362. //    Scope:
  1363. //        Public.
  1364. //
  1365. //    Parameters:
  1366. //        maxK        The amount of memory (in 1024-byte chunks) the caller wishes to
  1367. //                    preallocate for the user to record into.
  1368. //        quality        One of the standard Macintosh Sound Input Manager qualities: 'good',
  1369. //                    'betr', or 'best'.
  1370. //        doneFlag    A pointer to a Boolean by which the Helper will inform the caller
  1371. //                    that recording has finished and an SHGetRecordedSound call is in
  1372. //                    order.  If nil, then the caller will not be directly informed when
  1373. //                    recording is complete, but will instead have to call SHRecordStatus
  1374. //                    to find out.
  1375. //
  1376. //    Returns:
  1377. //        OSErr        If any of the stages fail.
  1378. //        noErr        Otherwise.
  1379. //
  1380. //    Operation:
  1381. //        This routine initiates asynchronous recording.  There are eight stages, each
  1382. //        of which could fail.  So, each state checks that the previous stage was success-
  1383. //        ful, so errors fall through the bottom.  Along the way, the local Booleans
  1384. //        deviceOpened and allocated are set when the sound input device is opened and
  1385. //        the sound input buffer is allocated, respectively.  If one or more of these flags
  1386. //        is set at the end of the routine AND there's been an error, then device closure/
  1387. //        deallocation is performed as required.  The stages are as follows:
  1388. //
  1389. //            1. Open the sound input device.
  1390. //            2. Ask the device if it can do asynchronous recording.
  1391. //            3. Allocate the sound input buffer, as specified by the maxK parameter.
  1392. //            4. Turn on metering and set the recording quality as specified by the
  1393. //               quality parameter.
  1394. //            5. The fifth stage is to grab (and save inside the gsInVars structure) the
  1395. //               default number of channels, sample rate, sample size, and compression
  1396. //               type.  We'll use the saved values later when we go to install a more
  1397. //               accurate header into the sound, at completion time.
  1398. //            6. Create a sound header in the sound input buffer.
  1399. //            7. Fill out the sound input parameter block inPB.  Note that we put the
  1400. //               application A5 into the userLong field of the parameter block, so that
  1401. //               SHRecordCompletion can access our globals.  Also note that
  1402. //               SHRecordCompletion is installed directly as the callback routine.  This
  1403. //               stage cannot fail, and sets err to noErr.
  1404. //            8. The eighth and final stage is to set various flags such that we know
  1405. //               we're recording, and calls SPBRecord to initiate the asynchronous
  1406. //               recording process.  The reason we set our flags before we make the
  1407. //               SPBRecord call is to avoid a "race" condition, where the recording could
  1408. //               (theoretically) complete virtually immediately and our callback routine
  1409. //               would get called before the flags were set up, thus confusing it.  To
  1410. //               avoid this, set the flags first, then the error handler code can reset
  1411. //               them if the recording failed.
  1412. //
  1413. //        If an error occurred along the way, the sound input device may be closed, and/or
  1414. //        the sound input buffer may be deallocated.
  1415. //
  1416. //=======================================================================================
  1417. pascal OSErr SHRecordStart(short maxK, OSType quality, Boolean *doneFlag)
  1418. {
  1419.     Boolean    deviceOpened = false;
  1420.     Boolean    allocated = false;
  1421.     
  1422.     OSErr    err;
  1423.     short    canDoAsync;
  1424.     short    metering;
  1425.     long    allocSize;
  1426.     
  1427.     // 1. Try to open the current sound input device
  1428.     err = SPBOpenDevice(nil,siWritePermission,&gsSHInVars.inRefNum);
  1429.     if (err == noErr)
  1430.         deviceOpened = true;
  1431.  
  1432.     // 2. Now let's see if this device an even handle asynchronous recording.
  1433.     if (err == noErr) {
  1434.         err = SPBGetDeviceInfo(gsSHInVars.inRefNum, siAsync, (Ptr)&canDoAsync);
  1435.         if (err == noErr && !canDoAsync)
  1436.             err = kSHErrNonAsychDevice;
  1437.     }
  1438.     
  1439.     // 3. Try to allocate memory for the guy's sound.
  1440.     if (err == noErr) {
  1441.         allocSize = (maxK * 1024) + kSHHeaderSlop;
  1442.         gsSHInVars.inHandle = NewHandle(allocSize);
  1443.         if (gsSHInVars.inHandle == nil) {
  1444.             err = MemError();
  1445.             if (err == noErr)
  1446.                 err = memFullErr;
  1447.         }
  1448.         if (err == noErr)
  1449.             allocated = true;
  1450.     }
  1451.         
  1452.     // 4. Set up various recording parameters (metering and quality)
  1453.     if (err == noErr) {
  1454.         metering = 1;
  1455.         SPBSetDeviceInfo(gsSHInVars.inRefNum, siLevelMeterOnOff, (Ptr)&metering);
  1456.         err = SPBSetDeviceInfo(gsSHInVars.inRefNum, siRecordingQuality, (Ptr)&quality);
  1457.     }
  1458.     
  1459.     // 5. Call SHGetDeviceSettings to determine a bunch of information we'll need to
  1460.     // make a header for this sound.
  1461.     if (err == noErr) {
  1462.         err = SHGetDeviceSettings(gsSHInVars.inRefNum, &gsSHInVars.numChannels,
  1463.             &gsSHInVars.sampleRate, &gsSHInVars.sampleSize, &gsSHInVars.compType);
  1464.     }
  1465.     
  1466.     // 6. Create a header for this sound.
  1467.     if (err == noErr) {
  1468.         err = SetupSndHeader((SndListHandle)gsSHInVars.inHandle, gsSHInVars.numChannels, gsSHInVars.sampleRate, gsSHInVars.sampleSize,
  1469.             gsSHInVars.compType, kSHBaseNote, allocSize, &gsSHInVars.headerLength);
  1470.     }
  1471.     
  1472.     // 7. Lock the input sound handle and set up the input parameter block.
  1473.     if (err == noErr) {
  1474.         MoveHHi(gsSHInVars.inHandle);
  1475.         HLock(gsSHInVars.inHandle);
  1476.         
  1477.         allocSize -= gsSHInVars.headerLength;
  1478.         gsSHInVars.inPB.inRefNum = gsSHInVars.inRefNum;
  1479.         gsSHInVars.inPB.count = allocSize;
  1480.         gsSHInVars.inPB.milliseconds = 0;
  1481.         gsSHInVars.inPB.bufferLength = allocSize;
  1482.         gsSHInVars.inPB.bufferPtr = *gsSHInVars.inHandle + gsSHInVars.headerLength;
  1483.         gsSHInVars.inPB.completionRoutine = gsSHRecordCompletionUPP;    // changed to UPP (BDM)
  1484.         gsSHInVars.inPB.interruptRoutine = nil;
  1485. #ifdef powerc
  1486.         gsSHInVars.inPB.userLong = 0L;
  1487. #else
  1488.         gsSHInVars.inPB.userLong = SetCurrentA5();        // for our completion routine
  1489. #endif    
  1490.         gsSHInVars.inPB.error = noErr;
  1491.         gsSHInVars.inPB.unused1 = 0;
  1492.         
  1493.         err = noErr;
  1494.     }
  1495.     
  1496.     // 8. Finally, if all went well, set our recording flag, make sure our record
  1497.     // completion flag is clear, and initiate asychronous recording.
  1498.     if (err == noErr) {
  1499.         gsSHInVars.recording = true;
  1500.         gsSHInVars.recordComplete = false;
  1501.         gsSHInVars.appComplete = doneFlag;
  1502.         gsSHInVars.paused = false;
  1503.         if (gsSHInVars.appComplete != nil)
  1504.             *gsSHInVars.appComplete = false;
  1505.         
  1506.         err = SPBRecord(&gsSHInVars.inPB, kSHAsync);
  1507.     }
  1508.     
  1509.     // Now clean up any errors that might have occurred.
  1510.     if (err != noErr) {
  1511.         gsSHInVars.recording = false;
  1512.         if (deviceOpened)
  1513.             SPBCloseDevice(gsSHInVars.inRefNum);
  1514.         if (allocated) {
  1515.             DisposeHandle(gsSHInVars.inHandle);
  1516.             gsSHInVars.inHandle = nil;
  1517.         }
  1518.     }
  1519.     
  1520.     return(err);
  1521. }
  1522.  
  1523. //=======================================================================================
  1524. //
  1525. //    pascal OSErr SHGetRecordedSound(Handle *theSound)
  1526. //
  1527. //    Summary:
  1528. //        This routine returns the sound handle from the last sound the Helper recorded.
  1529. //
  1530. //    Scope:
  1531. //        Public.
  1532. //
  1533. //    Parameters:
  1534. //        theSound        A pointer to a Handle by which to return the sound handle.
  1535. //
  1536. //    Returns:
  1537. //        kSHErrNoRecording        If there IS NO "last recorded sound"
  1538. //        OSErr                    If recording stopped because of an error
  1539. //        noErr                    Otherwise.
  1540. //
  1541. //    Operation:
  1542. //        This routine returns the sound handle (stored in the inHandle field of the
  1543. //        gsSHInVars struct) from the last sound that was recorded by the Helper.  If
  1544. //        the recording died by an error (other than abortErr), then the error is returned
  1545. //        and *theSound is set to nil.  SHGetRecordedSound is the method by which you
  1546. //        retrieve a sound handle, once you know (by whatever means) that recording is
  1547. //        complete.
  1548. //
  1549. //=======================================================================================
  1550. pascal OSErr SHGetRecordedSound(Handle *theSound)
  1551. {
  1552.     if (gsSHInVars.recordComplete) {
  1553.         if (gsSHInVars.recordErr != noErr) {
  1554.             *theSound = nil;
  1555.             return(gsSHInVars.recordErr);
  1556.         } else {
  1557.             *theSound = gsSHInVars.inHandle;
  1558.             return(noErr);
  1559.         }
  1560.     } else {
  1561.         *theSound = nil;
  1562.         return(kSHErrNoRecording);
  1563.     }
  1564. }
  1565.  
  1566. //=======================================================================================
  1567. //
  1568. //    pascal OSErr SHRecordStop(void)
  1569. //
  1570. //    Summary:
  1571. //        This routine immediately stops sound recording.
  1572. //
  1573. //    Scope:
  1574. //        Public.
  1575. //
  1576. //    Parameters:
  1577. //        None.
  1578. //
  1579. //    Returns:
  1580. //        OSErr        Whatever SPBStopRecording returns.
  1581. //
  1582. //    Operation:
  1583. //        This routine simply calls SPBStopRecording to immediately stop sound recording.
  1584. //        It sets the parameter block's error code to abortErr, and calls the callback
  1585. //        routine.  Use this routine to implement a "stop" button.
  1586. //
  1587. //=======================================================================================
  1588. pascal OSErr SHRecordStop(void)
  1589. {
  1590.     if (gsSHInVars.recording)
  1591.         return(SPBStopRecording(gsSHInVars.inRefNum));
  1592.     return noErr;
  1593. }
  1594.  
  1595. //=======================================================================================
  1596. //
  1597. //    pascal OSErr SHRecordPause(void)
  1598. //
  1599. //    Summary:
  1600. //        This routine pauses sound recording.
  1601. //
  1602. //    Scope:
  1603. //        Public.
  1604. //
  1605. //    Parameters:
  1606. //        None.
  1607. //
  1608. //    Returns:
  1609. //        kSHErrNotRecording        If we're not recording right now.
  1610. //        kSHErrAlreadyPaused        If recording is already paused.
  1611. //        OSErr                    Whatever SPBPauseRecording returns.
  1612. //
  1613. //    Operation:
  1614. //        This routine pauses sound recording if we ARE recording and we're not already
  1615. //        paused.  Use this routine to implement a "pause" button.
  1616. //
  1617. //=======================================================================================
  1618. pascal OSErr SHRecordPause(void)
  1619. {
  1620.     OSErr    err;
  1621.     
  1622.     if (gsSHInVars.recording) {
  1623.         if (!gsSHInVars.paused) {
  1624.             err = SPBPauseRecording(gsSHInVars.inRefNum);
  1625.             gsSHInVars.paused = (err == noErr);
  1626.             return(err);
  1627.         } else return(kSHErrAlreadyPaused);
  1628.     } else return(kSHErrNotRecording);
  1629. }
  1630.  
  1631. //=======================================================================================
  1632. //
  1633. //    pascal OSErr SHRecordContinue(void)
  1634. //
  1635. //    Summary:
  1636. //        This routine resumes recording when recording has previously been paused.
  1637. //
  1638. //    Scope:
  1639. //        Public.
  1640. //
  1641. //    Parameters:
  1642. //        None.
  1643. //
  1644. //    Returns:
  1645. //        kSHErrNotRecording        If we're not recording right now.
  1646. //        kSHErrAlreadyContinued    If we're not paused.
  1647. //        OSErr                    Whatever SPBResumeRecording returns.
  1648. //
  1649. //    Operation:
  1650. //        This routine continues sound recording if we ARE recording and we've been
  1651. //        previously paused.  Use this routine to implement a "unpause" button.
  1652. //
  1653. //=======================================================================================
  1654. pascal OSErr SHRecordContinue(void)
  1655. {
  1656.     OSErr    err;
  1657.     
  1658.     if (gsSHInVars.recording) {
  1659.         if (gsSHInVars.paused) {
  1660.             err = SPBResumeRecording(gsSHInVars.inRefNum);
  1661.             gsSHInVars.paused = !(err == noErr);
  1662.             return(err);
  1663.         } else return(kSHErrAlreadyContinued);
  1664.     } else return(kSHErrNotRecording);
  1665. }
  1666.  
  1667. //=======================================================================================
  1668. //
  1669. //    pascal OSErr SHRecordStatus(SHRecordStatusRec *recordStatus)
  1670. //
  1671. //    Summary:
  1672. //        This routine returns status information on sound that is being recorded.
  1673. //
  1674. //    Scope:
  1675. //        Public.
  1676. //
  1677. //    Parameters:
  1678. //        recordStatus    A pointer to a SHRecordStatusRec into which to store the
  1679. //                        recording status.
  1680. //
  1681. //    Returns:
  1682. //        kSHErrNotRecording        If we're not recording right now.
  1683. //        OSErr                    Whatever SPBGetRecordingStatus returns.
  1684. //        noErr                    Otherwise.
  1685. //
  1686. //    Operation:
  1687. //        If we're currently recording, we call SPBGetRecordingStatus.  It tells us lots
  1688. //        of handy things, most of which we return via recordStatus, then we give a status
  1689. //        of either shrPaused or shrRecording, depending upon the state of the
  1690. //        gsSHInVars.paused flag.  If recording is complete, we return shrFinished.  If an
  1691. //        error occurs, we give a status of shrError.
  1692. //
  1693. //=======================================================================================
  1694. pascal OSErr SHRecordStatus(SHRecordStatusRec *recordStatus)
  1695. {
  1696.     short            recStatus;
  1697.     OSErr            err;
  1698.     unsigned long    totalSamplesToRecord,numberOfSamplesRecorded;
  1699.     
  1700.     if (gsSHInVars.recording) {
  1701.         err = SPBGetRecordingStatus(gsSHInVars.inRefNum, &recStatus,
  1702.             &recordStatus->meterLevel, &totalSamplesToRecord, &numberOfSamplesRecorded,
  1703.             &recordStatus->totalRecordTime, &recordStatus->currentRecordTime);
  1704.         if (err == noErr)
  1705.             recordStatus->recordStatus = (gsSHInVars.paused ? shrPaused : shrRecording);
  1706.         else recordStatus->recordStatus = shrError;
  1707.         return(err);
  1708.     } else if (gsSHInVars.recordComplete) {
  1709.         recordStatus->recordStatus = shrFinished;
  1710.         recordStatus->meterLevel = 0;
  1711.         // Don't know about the other fields -- just leave 'em
  1712.         return(noErr);
  1713.     } else return(kSHErrNotRecording);
  1714. }
  1715.